package cn.iocoder.yudao.framework.excel.core.util;

import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import cn.iocoder.yudao.framework.excel.core.strategy.MixedColumnWidthStyleStrategy;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * Excel 工具类
 *
 * @author 芋道源码
 */
public class ExcelUtils {

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {
        write(response, filename, sheetName, head, data, null);
    }


    public static <T> void writeAuto(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data,Class<?> classs) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(new MixedColumnWidthStyleStrategy(classs)) // 注册自定义列宽策略
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param selectMap 下拉选择数据 Map<下拉所对应的列表名，下拉数据>
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data, List<KeyValue<ExcelColumn, List<String>>> selectMap) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        return EasyExcel.read(file.getInputStream(), head, null)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }


    /**
     * 过滤掉因导出模板的时候有下拉框导致读取的时候有空数据
     * @param file 文件
     * @param head 导入类型
     * @return 集合
     * @param <T> 不定义类型
     */
    public static <T> List<T> readNotNull(MultipartFile file, Class<T> head) throws IOException {
        InputStream inputStream = file.getInputStream();
        List<T> dataList = new ArrayList<>();
        AnalysisEventListener<T> listener = new AnalysisEventListener<T>() {
            @Override
            public void invoke(T data, AnalysisContext context) {
                // 在这里可以对每一行数据进行处理
                dataList.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                // 数据读取完成后的处理，可以在这里进行一些额外的操作
            }
        };
        // 设置自定义的过滤器
        CustomRowFilter<T> rowFilter = new CustomRowFilter<>(dataList);
        EasyExcel.read(inputStream, head, listener)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .registerReadListener(rowFilter) // 注册自定义的过滤器
                .sheet().doRead();

        // 返回非空数据列表
        return rowFilter.getFilteredData();

    }

    // 自定义过滤器，用于过滤空行
    static class CustomRowFilter<T> extends AnalysisEventListener<T> {
        private List<T> dataList;

        public CustomRowFilter(List<T> dataList) {
            this.dataList = dataList;
        }

        // 过滤后的非空数据列表
        private List<T> filteredData = new ArrayList<>();

        @Override
        public void invoke(T data, AnalysisContext context) {
            // 在这里进行空行检查
            // 这里假设数据行为空当且仅当所有字段都为空字符串
            if (!isEmptyRow(data)) {
                filteredData.add(data);
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 数据读取完成后的处理
        }

        public List<T> getFilteredData() {
            return filteredData;
        }

        // 判断是否是空行
        private boolean isEmptyRow(T data) {
            // 这里需要根据具体情况实现，判断一行数据是否为空
            // 这里假设所有字段都为空字符串表示空行
            // 你可以根据实际情况来判断空行的条件
            return data.toString().trim().isEmpty();
        }
    }


}
